Skip to content

SOLR-18212: Migrate PackageAPI endpoints from @EndPoint/@Command to JAX-RS - #4178

Open
epugh wants to merge 35 commits into
apache:mainfrom
epugh:copilot/migrate-packageapi-to-jax-rs
Open

epugh wants to merge 35 commits into
apache:mainfrom
epugh:copilot/migrate-packageapi-to-jax-rs

Conversation

@epugh

@epugh epugh commented Mar 2, 2026 •

Copy link
Copy Markdown
Contributor

Migrates org.apache.solr.pkg.PackageAPI from Solr's homegrown @EndPoint/@Command dispatch to standard JAX-RS annotations, consistent with other V2 APIs (ClusterFileStore, ZookeeperRead, ClusterProperty).

This has a pretty hefty update to the patterns, but I thought it was better to do it now while v2 api's are "experimental".

New REST endpoints

The command-based POST pattern is replaced with RESTful routes. Note the collection is now plural (/cluster/packages), matching the v2 API path conventions used elsewhere:

Old New
GET /cluster/package GET /cluster/packages
GET /cluster/package/{name} GET /cluster/packages/{name} (now returns 404 if the package doesn't exist, instead of silently returning an empty result)
POST /cluster/package {"add": {"package":"p","version":"v","files":[...]}} POST /cluster/packages/{name}/versions
POST /cluster/package {"delete": {"pkg":"p","version":"v"}} DELETE /cluster/packages/{name}/versions/{version}
POST /cluster/package {"refresh": "name"} POST /cluster/packages/{name}/refresh
(internal only, used by refresh for node-local reload) POST /cluster/packages/{name}/refresh?localOnly=true
(internal only, used to make a node catch up to a ZK version) POST /cluster/packages/sync?expectedVersion=N

The two internal-only rows replace an earlier version of this PR that smuggled the same inter-node signals onto GET /cluster/packages via refreshPackage/expectedVersion query params — a GET request shouldn't have side effects. localOnly mirrors the existing localDelete param on ClusterFileStoreApis.deleteFile.

Key structural changes

  • PackageAPI — new JAX-RS implementation (extends JerseyResource implements PackageApis); replaces the old Edit/Read inner classes (~220 lines removed)
  • PackageStore — renamed from the old PackageAPI; now clearly a ZK data/state holder (Packages, PkgVersion, ZK watcher), not an endpoint class. THIS WAS A BIG REFACTORING
  • solr/api module — new PackageApis.java (JAX-RS interface), AddPackageVersionRequestBody.java, and two response types instead of one: ListPackagesResponse (the GET /cluster/packages collection response, map-keyed by package name) and GetPackageResponse (the GET /cluster/packages/{name} single-resource response, a flat object for just that package) — matching the List<Resource>Response/Get<Resource>Response naming and shape convention used by ListAliasesResponse/GetAliasByNameResponse and similar pairs elsewhere in the v2 API. OAS generation produces PackageApi SolrJ request classes automatically.
  • PackageAPITest — integration test using SolrCloudTestCase (ZK required) with generated PackageApi SolrJ request classes
  • TestPackages — updated to use new endpoints; error path changed from /details[0]/errorMessages[0] to /msg
  • package-manager-internals.adoc — updated endpoint reference and curl examples
  • dev-docs/apis-v2-conventions.adoc — added a new "Listing vs. Single-Resource Responses" section documenting the List<Resource>Response/Get<Resource>Response convention, since this PR is the first to need it spelled out explicitly

Copilot AI and others added 8 commits March 1, 2026 13:42
- Replace PackagePayload.AddVersion with AddPackageVersionRequestBody
- Move package name from body to URL: POST /cluster/package/{name}/versions
- Replace delete command pattern with DELETE /cluster/package/{name}/versions/{version}
- Replace refresh command with POST /cluster/package/{name}/refresh
- Update errPath in testAPI from /details[0]/errorMessages[0] to /msg
- Remove all add.pkg assignments (pkg now lives in URL path)
- Replace add.pkg references in verifyComponent() with literal package name strings
- Update import: remove PackagePayload, add AddPackageVersionRequestBody (sorted)

Co-authored-by: Copilot <[email protected]>
@epugh
epugh marked this pull request as ready for review March 2, 2026 19:36
@gerlowskija

Copy link
Copy Markdown
Contributor

Is there a JIRA ticket associated with this change @epugh?

@epugh

epugh commented Apr 3, 2026

Copy link
Copy Markdown
Contributor Author

Is there a JIRA ticket associated with this change @epugh?

No, because I viewed it as "just a refactoring and no one will notice....", and that I was hoping there were no debates that would require a JIRA to hash out!

authors:
- name: Eric Pugh
links:
- name: PR#4178

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[0] Probably worth having a JIRA reference for this guy, even if it's just a tie-back to one of the v2 umbrella tickets? It's changing APIs and stuff, so worth tracking in more than just the PR description I'd imagine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOne

public int hashCode() {
return Objects.hash(version);
}
final var request =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] Is there a reason you're not using the auto-generated SolrJ type here? We should have one now that the API is JAX-RS!

If this was an intentional decision made to prevent scope-creep, I'm fine with that. Just making sure it's not oversight.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so many places to update... I suspect in the future we need to do a "Check each GSR for fitness" check ;-)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said, I think not doing these bits in the same PR is a totally valid choice. I don't want to add extra steps that'd apply to all these PRs. But since the lines were changed in this PR already I wanted to check that it was an intentional choice is all 🤷

@epugh epugh changed the title Migrate PackageAPI endpoints from @EndPoint/@Command to JAX-RS SOLR-18212: Migrate PackageAPI endpoints from @EndPoint/@Command to JAX-RS Apr 25, 2026
@epugh

epugh commented Apr 25, 2026 •

Copy link
Copy Markdown
Contributor Author

@gerlowskija I think I am ready for another review. I renamed PackageAPI to ClusterPackage to not conflict with PackageApis, but not married to that name! I am updating the spreadsheet.

@epugh

epugh commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

@gerlowskija I reopened this, and will try and give it some love today.. Thanks for the LGTM....

@epugh epugh removed stale PR not updated in 60 days closed-stale Closed after being stale for 60 days labels Aug 28, 2026
@epugh

epugh commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

@gerlowskija this is ready for review!!!!

@epugh
epugh requested a review from gerlowskija August 28, 2026 18:55
@epugh

epugh commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Tests, both unit and itest (bats) pass...

@jaykay12

Copy link
Copy Markdown
Contributor

@epugh I am planning to review this PR, can you make this one ready for review by rebasing with latest main?

@epugh

epugh commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

@jaykay12 I added you as a collaborate to my epugh/solr... which I think lets you push to this repo? I hope this makes it easier as I have a couple of in progress ones you may wan tot pick up!

epugh and others added 7 commits October 28, 2026 08:29
…n the Package v2 API

- Move the inter-node refresh/sync fan-out off GET /cluster/packages (which
  was mutating state via refreshPackage/expectedVersion query params) onto
  POST endpoints: a localOnly flag on the existing /refresh action (mirroring
  ClusterFileStoreApis.deleteFile's localDelete), and a new /sync action.
- getPackage() now returns 404 for an unknown package, matching
  deletePackageVersion/refreshPackage's existing NOT_FOUND behavior.
- Split the single-package GET response into its own flat shape
  (GetPackageResponse) instead of reusing the list endpoint's map-shaped
  response filtered to one entry, and renamed both response classes to match
  the List<Resource>Response / Get<Resource>Response convention used
  elsewhere (aliases, cluster properties, schema fields).
- Extracted the duplicated node-broadcast loop in ClusterPackage into a
  shared helper.
- Documented the List/Get response-naming convention in
  dev-docs/apis-v2-conventions.adoc.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019kzScQszwRcG4S7kA5Pa8b
PackageManager's deploy --update and undeploy flows were still POSTing a
v1-style JSON command body ({"refresh": "name"}) to /api/cluster/packages,
left over from before the JAX-RS migration. The new endpoint doesn't accept
a body at all - it's POST /cluster/packages/{name}/refresh - so these calls
were silently broken against the migrated server. Switch both call sites to
the generated PackageApi.RefreshPackage client, matching how
DeletePackageVersion/AddPackageVersion were already migrated elsewhere in
this file. Removes the now-unused PackageUtils.PACKAGE_PATH constant.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019kzScQszwRcG4S7kA5Pa8b
…kage API shape

testPayload()'s @EndPoint/@command fixture (path /cluster/package, an
AddVersion bean with pkg/version/files) mirrored the now-deleted v1 Package
API almost exactly, which reads as leftover Package API code rather than a
generic framework test. Rename to /foos/FooVersion/myfoo so it's obviously
just a test fixture.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019kzScQszwRcG4S7kA5Pa8b
ReferenceEquality flagged schemas[0] != schemas[1] as a likely mistake, but
the test intentionally checks whether the core picked up a new IndexSchema
instance after the package version bump - identity, not value equality, is
what's being verified. Suppress with the same pattern used elsewhere in the
codebase for intentional identity checks (SolrConfig.java,
CachingDirectoryFactory.java).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019kzScQszwRcG4S7kA5Pa8b
…sterPackage

addPackageVersion and deletePackageVersion each inlined the same "deserialize
packages.json bytes, fall back to a fresh Packages on missing/invalid data,
then copy()" block, and the same isEnabled()/BAD_REQUEST guard. Extracted
both into PackageStore (parseForUpdate, requireEnabled) since they operate on
PackageStore's own data/mapper, and call them from both atomicUpdate lambdas.

Also noted, via a one-line comment, why PackageManager's undeploy refresh
call site keeps a broader catch(Exception) than its sibling deploy call site
(it still wraps a postJsonToSolr call that declares throws Exception).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019kzScQszwRcG4S7kA5Pa8b
@epugh

epugh commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

@jaykay12 would love your review! I think this is ready for merging.

@jaykay12 jaykay12 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if this minor plan change makes sense?

Ask is to just isolate the { renaming of PackageAPI -> PackageStore } in a follow-up PR.

/**
* Holds package data loaded from ZooKeeper ({@code /solr/packages.json}) and manages ZK watchers.
*/
public class PackageStore {

@jaykay12 jaykay12 Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this class is majorly (PackageAPI - {code no longer needed}), please correct me if i am wrong.

[suggestion] should we be splitting up PRs in those case, so that review stays easier & any unintentional change gets caught.

This PR could have been just pure migration & removal of deprecated stuff from PackageAPI class. In subsequent follow-up this renaming could have been attempted?

This is my pure thought, with this minor change, the PR size would have reduced by atleast 8-9 files.

@Override
@PermissionName(PACKAGE_EDIT_PERM)
public SolrJerseyResponse addPackageVersion(
String packageName, AddPackageVersionRequestBody requestBody) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the core logic related to addVersion & deleteVersion be kept in PackageStore or PackageAPI for now (if earlier comment is chosen to be addressed)

that way, this class remains as thin adapter only.

return response;
}

private void syncToVersion(PackageStore packageStore, int expectedVersion) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be kept as package private function inside PackageStore?

[reason]
this function mutates - packageStore.pkgs, ideally we can keep that ownership from within packageStore? if this function moves inside packageStore, we can esure package.pkgs is private to that class & no one changes its state from outside other than its own functions.

solrClient, PackageUtils.PACKAGE_PATH, "{\"refresh\": \"" + packageName + "\"}");
} catch (Exception ex) {
new PackageApi.RefreshPackage(packageName).process(solrClient);
} catch (Exception ex) { // postJsonToSolr above declares a plain throws Exception

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code comment still valid?

/** Response for the get-a-specific-package API. */
public class GetPackageResponse extends SolrJerseyResponse {

@JsonProperty("result")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[info] why the key name as "result", why not just "packageInfo" here?

/** Response for the package listing API. */
public class ListPackagesResponse extends SolrJerseyResponse {

@JsonProperty("result")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[question] why not "packages" as the key?

@JsonProperty(required = true)
public List<String> files;
@JsonProperty("version")
@Schema(description = "The version string for this package version.", required = true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my local IntelliJ showed this warning.

Image

please see, if applicable here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, going to look....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants